home *** CD-ROM | disk | FTP | other *** search
/ IRIX 6.5 Applications 2002 November / SGI IRIX 6.5 Applications 2002 November.iso / dev / insight_dev.idb / usr / share / Insight / bin / indexgen_sgidocbk.z / indexgen_sgidocbk
Encoding:
Text File  |  2002-10-16  |  24.3 KB  |  852 lines

  1. #!/usr/bin/perl5
  2.  
  3. #
  4. # Copyright 2002, Silicon Graphics, Inc.
  5. # All Rights Reserved.
  6. #
  7. # This is UNPUBLISHED PROPRIETARY SOURCE CODE of Silicon Graphics, Inc.;
  8. # the contents of this file may not be disclosed to third parties, copied or
  9. # duplicated in any form, in whole or in part, without the prior written
  10. # permission of Silicon Graphics, Inc.
  11. #
  12. # RESTRICTED RIGHTS LEGEND:
  13. # Use, duplication or disclosure by the Government is subject to restrictions
  14. # as set forth in subdivision (c)(1)(ii) of the Rights in Technical Data
  15. # and Computer Software clause at DFARS 252.227-7013, and/or in similar or
  16. # successor clauses in the FAR, DOD or NASA FAR Supplement. Unpublished -
  17. # rights reserved under the Copyright Laws of the United States.
  18. #
  19. ####################################################################
  20. #
  21. #  Name: indexgen_sgidocbk
  22. #
  23. #  Note: PERL 5.004 or greater is required for this script.
  24. #  
  25. #  Function: scan an SGML file to find all instances of <indexterm>
  26. #    tags and use the information contained in those tags to
  27. #    create a separate file that contains an SGML index for the
  28. #    SGIDOCBK DTD that begins with the <index> tag and ends with
  29. #    the </index> tag.
  30. #
  31. #  Author: Adrian Daley
  32. #
  33. #  Other Information:
  34. #    When STDIN is used for input, <indexterm> tags without id attributes
  35. #    are ignored and not used in the index.  When a file is used for input
  36. #    the id attribute will be fixed on all <indexterm> tags that are
  37. #    lacking them if the '-q' command line argument is not used.
  38. #
  39. #  Version 0.5 - 9/28/98
  40. #    Initial version with most functionality and not much support for
  41. #    "see" and "seealso" parts of index terms.
  42. #
  43. #  Version 1.0 - 10/8/98
  44. #    Added support for "see" and "seealso" tags in <indexterm> and
  45. #    <indexentry> tags.  Refer to the ProcessTerm, CombineIdenticalTerms,
  46. #    and PrintIndex functions for more information on how they are
  47. #    handled.
  48. #
  49. #    Changed the <index> structure to use <indexdiv> structures with
  50. #    <title> tags to separate parts of the index that start with different
  51. #    letters.
  52. #
  53. #    Added -q command line option to bypass ID attribute checks
  54. #
  55. #    Added additional comments and documentation
  56. #
  57. #  Version 1.1 - 10/12/98
  58. #    Fixed bug related to <comment></comment> handling
  59. #    
  60. #    Now removes the following characters from sort as values to
  61. #    insure the proper sort order: '$', '/', '.', '<', '"', '-'
  62. #
  63. #  Version 1.2 - 3/1/99
  64. #    Changed CleanInput function to move any <indexterm> tags that
  65. #    occur within a <title> or <tbltitle> to just before the title.
  66. #    This is needed for the Inso stylesheet content() PVF.
  67. #
  68. #    Removed the use of <indexdiv> tags in the output to
  69. #    reduce the dependencies on specific languages
  70. #
  71. #    Added some hints as to how to localize the program
  72. #
  73. #  Version 1.3 - 02Mar99        (Ferg / gferg@sgi.com)
  74. #    Localization work; locale map and setlocale() implemented
  75. #
  76. ####################################################################
  77.  
  78. # localization; see perllocale(1) for details
  79. #
  80. # perl 5.004 can be installed from freeware.sgi.com
  81. #
  82. require 5.004;
  83.  
  84. # use the locale for the life of the program
  85. #
  86. use locale;
  87. use POSIX qw(locale_h);
  88. use POSIX qw(strcoll);
  89.  
  90.  
  91. my($input_file, $output_file);
  92. local($verbose) = 1;
  93. my(@terms, @sorted_terms);
  94. my($total, $unique_entries);
  95. my($lang) = '';
  96. my($locale) = '';
  97.  
  98. # Mapping from SGIDOCBK LANG attrib to supported IRIX system locales
  99. #
  100. # Note : We may want to expand the left-hand side to include additional
  101. #        variations
  102. #
  103. # See  : http://localize.engr/root/products/IRIXLocales/locales_6_5.html
  104. #
  105. my(%locales) = (
  106.         'C'        =>    'C',        # English
  107.         'en'        =>    'C',        # English
  108.         'de'        =>    'de',        # German
  109.         'fr'        =>    'fr',        # French
  110.         'es'        =>    'es',        # Spanish
  111.         'jp'        =>    'ja_JP.EUC',    # Japanese
  112.         'ja_JP'        =>    'ja_JP.EUC',    # Japanese
  113.         'ja_JP.EUC'    =>    'ja_JP.EUC',    # Japanese
  114.         'ja_JP.ujis'    =>    'ja_JP.EUC',    # Japanese
  115.         'ja_JP.eucJP'    =>    'ja_JP.EUC',    # Japanese
  116.         'ja_JP.SJIS'    =>    'ja_JP.SJIS',    # Japanese (shift-JIS)
  117.         'ja.SJIS'    =>    'ja_JP.SJIS',    # Japanese (shift-JIS)
  118.         'zh_TW'        =>    'zh_TW.ucns',    # Traditional Chinese
  119.         'zh_TW.ucns'    =>    'zh_TW.ucns',    # Traditional Chinese
  120.         'zh_TW.EUC'    =>    'zh_TW.ucns',    # Traditional Chinese
  121.         'zh_TW.big5'    =>    'zh_TW.big5',    # Traditional Chinese
  122.         'zh_CN'        =>    'zh_CN.ugb',    # Simplified Chinese
  123.         'zh_CN.ugb'    =>    'zh_CN.ugb',    # Simplified Chinese
  124.         'zh_CN.EUC'    =>    'zh_CN.ugb',    # Simplified Chinese
  125.         'zh_CN.gbk'    =>    'zh_CN.gbk',    # Simplified Chinese
  126.         'zh_CN.eucgbk'    =>    'zh_CN.gbk',    # Simplified Chinese
  127.         'ko'        =>    'ko_KR.euc',    # Korean
  128.         'ko_KR'        =>    'ko_KR.euc',    # Korean
  129.         'ko_KR.euc'    =>    'ko_KR.euc',    # Korean
  130.         'ko_KR.eucKR'    =>    'ko_KR.euc'    # Korean
  131.         );
  132.  
  133. # Global delimiter value for strings
  134. # This value should be see such that it never will occur in a string
  135. $delimiter = ':%:%:';
  136.  
  137. ($input_file, $output_file, $verbose) = ProcessArgs();
  138.  
  139. # Read the input file into a single string replacing newlines with spaces
  140. if($verbose) { print STDERR "\tReading file...\n"; }
  141. $buffer = '';
  142. while($line = <$input_file>) {
  143.     chomp $line;
  144.     $buffer .= $line.' ';
  145. }
  146.  
  147. #remove all <comment></comment> sections in case they contain <indexterm> tags
  148. $buffer =~ s#<comment[^>]*>.*?</comment>##img;
  149.  
  150. # find the locale for this document from the <sgidocbk> LANG attribute.
  151. #
  152. if($buffer =~ /<sgidocbk[^>]*LANG\s*=\s*"([^"]+)"/im) {
  153.     $lang = $1;
  154. } else {
  155.     $lang = "C";
  156. }
  157.  
  158.  
  159. # cannot find the correct locale for the specified book LANG
  160. #
  161. if(($locale = $locales{$lang}) eq '') {
  162.     print STDERR "\tWARNING: Locale for LANG attribute '$lang' was ",
  163.              "not found; 'C' will be used.\n";
  164.     $locale = 'C';
  165. }
  166.  
  167. if($verbose) { print STDERR "\tUsing LANG='$lang' with locale='$locale'\n"; } 
  168.  
  169. # set the proper locale
  170. #
  171. # LC_CTYPE   needed for uc(), lc(), ucfirst(), lcfirst()
  172. # LC_COLLATE needed for lt, le, cmp, ge, gt, strcoll(), sort()
  173. #
  174. if( !setlocale(LC_CTYPE, $locale) ) {
  175.     print STDERR "\tWARNING: setlocale() for locale '$locale' failed; ",
  176.              "'C' will be used.\n";
  177.     setlocale(LC_CTYPE, 'C');
  178.     setlocale(LC_COLLATE, 'C');
  179. } else {
  180.     setlocale(LC_COLLATE, $locale);
  181. }
  182.  
  183.  
  184. # iteratively find all of the <indexterm>s and create an index entry for them
  185. if($verbose) { print STDERR "\tParsing <indexterms>...\n"; }
  186. while($buffer =~ m#<indexterm([^>]*)>(.+?)</indexterm>#ios) {
  187.         $result = ProcessTerm($1, $2);
  188.         if(defined($result)) {
  189.                 push(@terms, $result);
  190.         }
  191.         # only need to look at the remainder of the buffer now
  192.         $buffer = $';
  193. }
  194.  
  195. $total = $#terms + 1;
  196.  
  197. if($total == 0) {
  198.     print STDERR "\tNo index terms found.  No index will be created.\n";
  199.     exit(0);
  200. }
  201.  
  202. if($verbose) { print STDERR "\tSorting $total terms...\n"; }
  203. @sorted_terms = sort by_alpha @terms;
  204.  
  205. if($verbose) { print STDERR "\tCombining duplicate terms...\n"; }
  206. $unique_entries = CombineIdenticalTerms(\@sorted_terms);
  207.  
  208. if($verbose) { print STDERR "\tPrinting $unique_entries unique terms...\n"; }
  209. PrintIndex(\@sorted_terms, $output_file, $lang);
  210.  
  211. print STDERR "\tFinished: $total terms found and indexed in $unique_entries unique entries.\n";
  212.  
  213. ### END MAIN PROGRAM ###
  214.  
  215. ###############################################################################
  216. #
  217. #    Read in the command line arguments.  Open the input and output
  218. #    file handles and return references to them.  If the input source
  219. #    is a file, then call CleanInput to check and fix the ID attribute
  220. #    on the file.  If the input is STDIN and/or the output is STDOUT,
  221. #    references to the respective filehandle will be returned.  The "-s"
  222. #    argument determines if verbose status reports are not generated.
  223. #    If the -q argument is used, checking and fixing attribute tags in the
  224. #    input file will be bypassed for faster processing if the user knows
  225. #    it is not needed.
  226. #
  227. #    Returns: (filehandle input, filehandle output, boolean verbose_status)
  228. #
  229. ###############################################################################
  230. sub ProcessArgs() {
  231.     local($input) = "";
  232.     local($output) = "";
  233.     local($verbose) = 1;
  234.     local($do_cleanup) = 1;
  235.  
  236.     while($arg = shift(@ARGV)) {
  237.         if($arg =~ /^-h/) { Usage(); }
  238.         elsif($arg =~ /^-q/) { $do_cleanup = 0; }
  239.         elsif($arg =~ /^-s/) { $verbose = 0; }
  240.         elsif($arg =~ /^-i/) { 
  241.             $input = shift(@ARGV); 
  242.             if($input eq "") {
  243.                 Usage();
  244.             } elsif(! -e $input) {
  245.                 print "\nInput file doesn't exist!\n";
  246.                 exit(1);
  247.             }
  248.         }
  249.         elsif($arg =~ /^-o/) { 
  250.             $output = shift(@ARGV); 
  251.             if($output eq "") {
  252.                 print "\nInput file name not specified correctly";
  253.                 Usage();
  254.             }
  255.         } else {
  256.             print STDERR "\nThe argument '$arg' is not supported.";
  257.             Usage();
  258.         }
  259.     }
  260.  
  261.     # open the input and output files
  262.     if($input ne '') {
  263.         if($do_cleanup) {
  264.             # first clean up the file to make sure every <indexterm> as an id attribute
  265.             if($verbose) { print STDERR "\tCorrecting <indexterm> id attributes...\n"; }
  266.             CleanInput($input);
  267.         }
  268.         open(INPUT, "$input") || die "Unable to open input file: $input\n";
  269.         $input = \*INPUT;
  270.     } else {
  271.         if($verbose) { print STDERR "\tReading from STDIN\n"; }
  272.         $input = \*STDIN;
  273.     }
  274.  
  275.     if($output ne '') {
  276.         open(OUTPUT, ">$output") || die "Unable to open output file: $output\n";
  277.         $output = \*OUTPUT;
  278.     } else {
  279.         if($verbose) { print STDERR "\tWriting to STDOUT\n"; }
  280.         $output = \*STDOUT;
  281.     }
  282.  
  283.     return($input, $output, $verbose);
  284. }
  285.  
  286. #################################################################################
  287. #
  288. #    Parses an <indexterm> content to gather the <primary>,<secondary>,<tertiary>
  289. #    terms, primary, secondary, tertiary sort as attribute values, a
  290. #    single <see> tag content and the content of multiple <seealso> tags.
  291. #
  292. #    After parsing the input for the above values, all of the values are cleaned
  293. #    up to remove extra spaces.  For terms with no sort as values, the respective
  294. #    term is used with all SGML tags removed.
  295. #
  296. #    Input:
  297. #        $term - string of the indexterm tag (ex. '<indexterm ID="???">')
  298. #        $content - the entire string between the <indexterm> and </indexterm>
  299. #
  300. #    Return Value: a reference to an array with the following information.  If
  301. #        a valid indexterm is not found, the value undef is returned.
  302. #
  303. #        0 - the indexterm's ID attribute value - if a <see> value was
  304. #            found, this value will be ''.    
  305. #        1 - <primary> tag content
  306. #        2 - <secondary> tag content
  307. #        3 - <tertiary> tag content
  308. #        4 - primary sort as value
  309. #        5 - secondary sort as value
  310. #        6 - tertiary sort as value
  311. #        7 - <see> tag content
  312. #        8 - <seealso> tag(s) content.  
  313. #            Multiple values are joined with '$delimiter'
  314. #
  315. #################################################################################
  316. sub ProcessTerm {
  317.     local($term, $content) = @_;
  318.     local($id, $primary, $secondary, $tertiary, $p_sort, $s_sort, $t_sort, $temp, $see, $seealso);
  319.  
  320.     if($term =~ m#id="([^"]+)#i) {
  321.         $id = $1;
  322.     } else {
  323.         print STDERR "Warning: ID attribute not found in $content. Skipping...\n";
  324.         return(undef);
  325.     }
  326.  
  327.     if($content =~ m#<primary([^>]*)>(.+?)</primary>#i) {
  328.         $primary = $2;
  329.         $temp = $1;
  330.         if($temp =~ m#sortas="([^"]+)#i) {
  331.             $p_sort = $1;
  332.         }
  333.  
  334.         if($content =~ m#<secondary([^>]*)>(.+?)</secondary>#i) {
  335.             $secondary = $2;
  336.             $temp = $1;
  337.             if($temp =~ m#sortas="([^"]+)#i) {
  338.                 $s_sort = $1;
  339.             }
  340.                 
  341.             if($content =~ m#<tertiary([^>]*)>(.+?)</tertiary>#i) {
  342.                 $tertiary = $2;
  343.                 $temp = $1;
  344.                 if($temp =~ m#sortas="([^"]+)#i) {
  345.                     $t_sort = $1;
  346.                 }
  347.             } 
  348.         }
  349.  
  350.         # search for <see> and <seealso> tags
  351.         if($content =~ m#<see[^>]*>(.+?)</see>#i) {
  352.             $see = $1;
  353.         }
  354.  
  355.         $seealso = '';
  356.         while($content =~ s#<seealso[^>]*>(.+?)</seealso>##i) {
  357.             if($seealso eq '') {
  358.                 $seealso = $1;
  359.             } else {
  360.                 $seealso .= "$delimiter$1";
  361.             }
  362.         }
  363.     } else {
  364.         print STDERR "Warning: Invalid primary indexterm in $id. Skipping...\n";
  365.         $primary = '';
  366.     }
  367.  
  368.     if($primary ne '' && $id ne '') {
  369.         # set, clean-up, and modify the ?_sort variables
  370.         if($p_sort eq '') {
  371.             $p_sort = $primary;
  372.         }
  373.         if($s_sort eq '') {
  374.             $s_sort = $secondary;
  375.         }
  376.         if($t_sort eq '') {
  377.             $t_sort = $tertiary;
  378.         }
  379.  
  380.         @new_term = ($id, $primary, $secondary, $tertiary, $p_sort, $s_sort, $t_sort, $see, $seealso);
  381.  
  382.         # remove extra whitespace before and after the entries
  383.         for $i (1..8) {
  384.             $new_term[$i] =~ s/^\s+|\s+$//g;
  385.         }
  386.  
  387.         for $i (4..6) {
  388.             # remove extraneous tags from the sortas terms
  389.             $new_term[$i] =~ s/<[^>]*>/ /g;
  390.             $new_term[$i] =~ s/\s+/ /g;
  391.  
  392.             # remove extra characters that may affect sorting
  393.              $new_term[$i] =~ s/[\/\.\$<"-]//g;
  394.  
  395.             # Double check to remove extra whitespace
  396.             $new_term[$i] =~ s/^\s+|\s+$//g;
  397.  
  398.             # convert all sortas terms to lower case.
  399.             $new_term[$i] = lc($new_term[$i]);
  400.         }
  401.  
  402.         # remove the ID attribute field for terms with a "see" value
  403.         if($new_term[7] ne '') {
  404.             $new_term[0] = '';
  405.         }
  406.  
  407.         if($new_term[4] eq '') {
  408.             # skip terms that only have SGML tags as their content
  409.             return(undef);
  410.         } else {
  411.             # return a reference to the array of indexterm information
  412.             return([@new_term]);
  413.         }
  414.     } else {
  415.         return(undef);
  416.     }
  417. }
  418.  
  419. ####################################################################
  420. #
  421. #    Sorting routine to determine the ordering of an array of
  422. #    index terms.  $a and $b sort items are always references to
  423. #    the array described in the ProcessTerm function.  All terms
  424. #    are sorted using only their sort as value.
  425. #
  426. ####################################################################
  427. sub by_alpha {
  428.     local($result, $count);
  429.  
  430.     # loop through the primary, secondary, and tertiary sort as values.
  431.  
  432.     # the function returns as soon as a result is found because the two terms
  433.     # are not identical at some level.  If they are identical, the terms are
  434.     # sorted by numeric reference values to make the sort well defined.
  435.  
  436.     $result = 0;
  437.     $count = 1;
  438.     while($result == 0 && $count <= 3) {
  439.  
  440.         $result = strcoll($a->[$count+3], $b->[$count+3]);
  441.         $count += 1;
  442.     }
  443.  
  444.     if($result != 0) {
  445.         return($result);
  446.     } else {
  447.         # last ditch sorting on otherwise equivalent terms to make
  448.         # sure the sort order is well defined.
  449.         return (strcoll($a, $b));
  450.     }
  451. }
  452.  
  453. ####################################################################
  454. #
  455. #    CombineIdenticalTerms looks for duplicate index entries
  456. #    (the same index term at more than one location in the book)
  457. #    and combines it into a single entry that will appear in the
  458. #    index with multiple references.  This function assumes the
  459. #    array of terms has already been sorted so that identical terms
  460. #    occur consecutively.  
  461. #
  462. #    Two terms are considered identical if their primary, secondary,
  463. #    and tertiary sortas terms are identical.  If the sortas term
  464. #    is not explictly specified, it is the term with all SGML markup
  465. #    removed.
  466. #
  467. #    Terms are combined by joining their ID attribute values with
  468. #    $delimiter in the 0 array location.  Then the "see" and "see also"
  469. #    attributes are combined.  Finally, the extra occurance of the
  470. #    term is removed from the array by setting its value to undef.
  471. #
  472. #    Note: Combined terms with different SGML markup in the index term
  473. #    will use the SGML tags for the first occurence of the tag.
  474. #
  475. ####################################################################
  476. sub CombineIdenticalTerms {
  477.     local($terms) = @_;
  478.     local($count);
  479.     local($term, $prev_term);
  480.  
  481.     $count = $#terms + 1;
  482.  
  483.     $prev_term = $terms->[0];
  484.  
  485.     for $i (1 .. $#terms) {
  486.         $term = $terms->[$i];
  487.  
  488.         if($prev_term->[4] eq $term->[4] && 
  489.            $prev_term->[5] eq $term->[5] && 
  490.            $prev_term->[6] eq $term->[6])
  491.         {
  492.             # combine "see" values
  493.             if($term->[7] ne '') {
  494.                 if($prev_term->[7] ne '') {
  495.                     $prev_term->[7] .= "$delimiter$term->[7]";
  496.                 } else {
  497.                     $prev_term->[7] = $term->[7];
  498.                 }
  499.             }
  500.  
  501.             # combine the "see also" values
  502.             if($term->[8] ne '') {
  503.                 if($prev_term->[8] ne '') {
  504.                     $prev_term->[8] .= "$delimiter$term->[8]";
  505.                 } else {
  506.                     $prev_term->[8] = $term->[8];
  507.                 }
  508.             }
  509.             
  510.             # combine the ID attribute values
  511.             if($term->[0] ne '') {
  512.                 if($prev_term->[0] ne '') {
  513.                     $prev_term->[0] .= "$delimiter$term->[0]";
  514.                 } else {
  515.                     $prev_term->[0] = $term->[0];
  516.                 }
  517.             }
  518.  
  519.             # remove the duplicate term
  520.             $terms->[$i] = undef;
  521.  
  522.             #decrease the count of unique entries
  523.             --$count;
  524.         } else {
  525.             $prev_term = $term;
  526.         }
  527.     }
  528.     return($count);
  529. }
  530.  
  531. ##########################################################################
  532. #
  533. #    Prints the SGIDOCBK SGML index.  The output file contains a valid
  534. #    index starting with an <index> tag and closing with a </index> tag
  535. #
  536. #    The overall format of the index is:
  537. #    <INDEX>
  538. #        <INDEXENTRY></INDEXENTRY> (one entry for each term)
  539. #    </INDEX>
  540. #
  541. #    where index entries are formatted as:
  542. #    <INDEXENTRY>
  543. #    <PRIMARYIE></PRIMARYIE> - 1 and only 1
  544. #        <SEEIE></SEEIE> - 0 or more
  545. #        <SEEALSOIE></SEEALSOIE> - 0 or more
  546. #        <SECONDARYIE></SECONDARYIE> - 0 or more
  547. #        <SEEIE></SEEIE> - 0 or more
  548. #        <SEEALSOIE></SEEALSOIE> - 0 or more
  549. #        <TERTIARYIE></TERTIARYIE> - 0 or more after each <SECONDARYIE>
  550. #            <SEEIE></SEEIE> - 0 or more
  551. #            <SEEALSOIE></SEEALSOIE> - 0 or more
  552. #    </INDEXENTRY>
  553. #
  554. #    Note: <XREF> tags are inserted as needed in the <PRIMARYIE>,
  555. #        <SECONDARYIE>, and <TERTIARYIE> tags to create links to
  556. #        the appropriate location in the book.
  557. #
  558. ##########################################################################
  559. sub PrintIndex {
  560.     my($terms, $output, $lang) = @_;
  561.     local($term, $count, $term_level, $prev_term);
  562.     local(@current_open_term);
  563.     local($create_indexdiv) = 0;
  564.     local($current_letter) = '';
  565.  
  566.     # $lang isn't set correctly in all cases and can't be used
  567.     #if($lang eq 'C' || $lang eq 'en') {
  568.     #    $create_indexdiv = 1;
  569.     #}
  570.  
  571.     @levels = ('IGNORED', 'PRIMARYIE', 'SECONDARYIE', 'TERTIARYIE');
  572.  
  573.     print $output "<INDEX>\n\n";
  574.  
  575.     for $i (0 .. $#terms) {
  576.         $term = $terms->[$i];
  577.  
  578.         # skip invalid, undefined entries
  579.         if(! defined($term)) { next; }
  580.  
  581.         # determine which term level should get the <xrefs> added
  582.         $count = 0;
  583.         for $j (1..3) {
  584.             if($term->[$j] ne '') { 
  585.                 ++$count; 
  586.             }
  587.         }
  588.  
  589.         $term_level = 1;
  590.  
  591.         while($term->[$term_level] ne '' && ($term_level >= 1 && $term_level <= 3)) {
  592.             if($current_open_term[$term_level] eq $term->[$term_level+3]) {
  593.                 ++$term_level;
  594.                 next;
  595.             } else {
  596.                 if($term_level == 1) {
  597.                     # trick to not open an <indexentry> tag
  598.                     # at the beginning of the document
  599.                     if($i > 0) {
  600.                         print $output "</INDEXENTRY>\n\n";
  601.                         # reset open term array
  602.                         @current_open_term = ();
  603.                     }
  604.  
  605.                     # If <indexdiv> tags should be used to group the
  606.                     # index entries, used the first letter of the primary
  607.                     # term to determine the divisions.  Since the terms are
  608.                     # already sorted, they should just go along in ASCII order.
  609.                     if($create_indexdiv) {
  610.                         $new_letter = uc(substr($term->[4], 0, 1));
  611.                         if($new_letter !~ m#^[A-Z]$#) {
  612.                             if($new_letter =~ m#^[0-9]$#) {
  613.                                 $new_letter = 'Numbers';
  614.                             } else {
  615.                                 $new_letter = 'Symbols';
  616.                             }
  617.                         }
  618.                         if($current_letter ne $new_letter) {
  619.                             if($current_letter ne '') {
  620.                                 print $output "</INDEXDIV>\n\n";
  621.                             }
  622.                             print $output "<INDEXDIV id=\"sgi-index-$new_letter\">\n";
  623.                             print $output "<TITLE>$new_letter</TITLE>\n";
  624.                             $current_letter = $new_letter;
  625.                         }
  626.                     }
  627.  
  628.                     print $output "<INDEXENTRY>\n";
  629.                 }
  630.             }
  631.  
  632.             # indent tags to improve readiblity
  633.             for $k (1..($term_level*3)) { print $output " "; }
  634.  
  635.             print $output "<$levels[$term_level]>$term->[$term_level]";
  636.             $current_open_term[$term_level] = $term->[$term_level+3];
  637.  
  638.             # if we're at the proper level add the <xrefs>
  639.             # $term->[0], the ID value may have multiple entries
  640.             # separated by "$delimiter"
  641.             if($count == $term_level && $term->[0] ne '') {
  642.                 @refs = split(/$delimiter/, $term->[0]);
  643.                 foreach $ref (@refs) {
  644.                     next if($ref eq '');
  645.                     print $output " <XREF LINKEND=\"$ref\">";
  646.                 }
  647.             }
  648.             print $output "</$levels[$term_level]>\n";
  649.  
  650.             # print out any <SEEIE> and <SEEALSOIE> tags
  651.             if($count == $term_level) {
  652.                 if($term->[7] ne '') {
  653.                     $term->[7] = RemoveDuplicates($term->[7]);
  654.                     @refs = split(/$delimiter/, $term->[7]);
  655.                     foreach $ref (@refs) {
  656.                         next if($ref eq '');
  657.                         for $k (1..(($term_level+1)*3)) { print $output " "; }
  658.                         print $output "<SEEIE>$ref</SEEIE>\n";
  659.                     }
  660.                 }
  661.  
  662.                 if($term->[8] ne '') {
  663.                     $term->[8] = RemoveDuplicates($term->[8]);
  664.                     @refs = split(/$delimiter/, $term->[8]);
  665.                     foreach $ref (@refs) {
  666.                         next if($ref eq '');
  667.                         for $k (1..(($term_level+1)*3)) { print $output " "; }
  668.                         print $output "<SEEALSOIE>$ref</SEEALSOIE>\n";
  669.                     }
  670.                 }
  671.             }
  672.             ++$term_level;
  673.         }
  674.     }
  675.  
  676.     # close the last <indexentry> tag if any existed
  677.     if($#terms != -1) {
  678.         print $output "</INDEXENTRY>\n\n";
  679.         if($create_indexdiv) {
  680.             print $output "</INDEXDIV>\n";
  681.         }
  682.     }
  683.     print $output "</INDEX>\n";
  684. }
  685.  
  686. ########################################################################
  687. #
  688. #    Given a string of multiple values separated by "$delimiter" remove any
  689. #    duplicate values and return a string of unique values separated by
  690. #    "$delimiter" and sorted in case-insensitive, ASCII order.  Any SGML tags
  691. #    that may exist in the values are not used to determine identical
  692. #    values.
  693. #
  694. ########################################################################
  695. sub RemoveDuplicates {
  696.     local($string) = @_;
  697.     local(%hash);
  698.  
  699.     @parts = split(/$delimiter/, $string);
  700.     foreach $part (@parts) {
  701.         $key = $part;
  702.  
  703.         # remove SGML tags and extra spaces from the key values
  704.         # used to determine if two values are identical.
  705.         $key =~ s/<[^>]*>/ /g;
  706.         $key =~ s/\s+/ /g;
  707.         $key =~ s/^\s+|\s+$//g;
  708.  
  709.         $hash{$key} = $part;
  710.     }
  711.  
  712.     # LOCALIZE: the returned values should be sorted according to the best locale
  713.     return(join("$delimiter", sort { lc($a) cmp lc($b) } (values(%hash))));
  714. }
  715.  
  716. #########################################################################
  717. #
  718. #    Given a file name, the <indexterm> tags in the file will be
  719. #    checked for valid ID attributes.  Tags will invalid or missing
  720. #    attribute values will have new, unique values provided.
  721. #
  722. #    Note: to differentiate generated attributes from existing ones
  723. #    all generated values start with "IG"
  724. #
  725. #    Input: file name of the file that needs to be verified.
  726. #
  727. #    Returns: nothing
  728. #
  729. #########################################################################
  730. sub CleanInput {
  731.     local($file) = @_;
  732.     local($id_count) = 0;
  733.     local($buffer) = "";
  734.     
  735.     open(INPUT, "$file") || die "Can't open input file $input for clean up.\n";
  736.         while($line = <INPUT>) {
  737.                 $buffer .= $line;
  738.         }
  739.         close(INPUT);
  740.  
  741.     $newbuffer = '';
  742.     while($buffer =~ m#<(title|tbltitle)[^>]*>.*?<\/\1>#ims) {
  743.         $newbuffer .= $`;
  744.         $title = $&;
  745.         $buffer = $';
  746.         $terms = '';
  747.         while($title =~ s#(<indexterm[^>]*>.*?<\/indexterm>)##ims) {
  748.             $terms .= $1;
  749.             # make sure the changes are saved
  750.             $id_count = 1;
  751.         }
  752.         # remove extra newlines left over from the <indexterm> tags
  753.         $title =~ s#^\n+|\n+$##imsg;
  754.         $newbuffer .= "$terms\n$title";
  755.     }
  756.     $newbuffer .= $buffer;
  757.     $buffer = undef;
  758.  
  759.     @chunks = split(/(<\/indexterm[^>]*>)/im, $newbuffer);
  760.     $newbuffer = undef;
  761.     for $i (0 .. $#chunks) {
  762.         $line = $chunks[$i];
  763.         if($line =~ /<indexterm([^>]*)>/im) {
  764.             $temp = $1;
  765.             if($temp =~ /id="([^"]*)"/im) {
  766.                 $id = $1;
  767.                 if($id =~ /[A-Za-z].*/) {
  768.                     # good id value, no changes needed
  769.                 } else {
  770.                     # correct bad ID value
  771.                     $id = "IG".$$.$id_count;
  772.                     ++$id_count;
  773.                     $line =~ s/(<indexterm.*id=")[^"]*/$1$id/im;
  774.                 }
  775.             } else {
  776.                 # no id attribute found - create a new one
  777.                 $id = "IG".$$.$id_count;
  778.                 ++$id_count;
  779.                 $line =~ s/(<indexterm[^>]*)/$1 ID="$id"/i;
  780.             }
  781.         }
  782.         $chunks[$i] = $line;
  783.     }
  784.  
  785.     if($id_count > 0) {
  786.         # need to save changes
  787.         open(OUTPUT, ">$file") || die "Can't open output file: $file for clean up\n";
  788.         print OUTPUT join("", @chunks);
  789.         close(OUTPUT);
  790.     }
  791. }
  792.  
  793. ####################################################################
  794. #
  795. #    Prints the program's usage statement and exits.
  796. #
  797. ####################################################################
  798. sub Usage {
  799.  
  800. $name = $0;
  801. $name =~ s#.*/##g;
  802.  
  803. print <<END_USAGE;
  804.  
  805. $name Version 1.3
  806.  
  807. Usage: $name [-h] [-s] [-o <FileName>] [-i <FileName>]
  808.  
  809.   -h         Print this help message
  810.   -i <filename> Read input from specified file rather than STDIN
  811.   -o <filename>    Write output to specified file rather than STDOUT
  812.   -s        Silent mode; don't print update messages to STDERR
  813.   -q        Quick; don't attempt to check/fix <indexterm> ID attributes
  814.             Invalid <indexterm> tags will be skipped.
  815.  
  816. END_USAGE
  817.  
  818.     exit(1);
  819.  
  820. }
  821.  
  822. ####################################################################
  823. #
  824. #    Prints the contents of an index term entry.
  825. #    This should only be used for debugging.
  826. #
  827. #    Input: a reference to the entry array location
  828. #
  829. ####################################################################
  830. sub DebugEntry {
  831.  
  832.     print "\n----------------------------------------------\n";
  833.  
  834.     local($ref) = @_;
  835.     if(! defined($ref)) {
  836.         print "DebugEntry - undefined reference\n";
  837.         return;
  838.     }
  839.  
  840.     # LOCALIZE
  841.     # may need to dereference collated array entries
  842.     foreach $i (0..8) {
  843.         print "$i - $ref->[$i]\n";
  844.     }
  845.  
  846.     print "-------------------------------------------------\n";
  847. }
  848.  
  849. ####################################################################
  850.  
  851.  
  852.